/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using NDODS.DAP.Exceptions;
using NDODS.Util;
using DASParser = NDODS.DAP.Parser.DASParser;

namespace NDODS.DAP
{
	
	/// <summary> The Data Attribute Structure is a set of name-value pairs used to
	/// describe the data in a particular dataset.  The name-value pairs
	/// are called the "attributes."  The values may be of any of the
	/// OPeNDAP simple data types (DByte, DInt32, DUInt32, DFloat64, DString and
	/// DURL), and may be scalar or vector.  (Note that all values are
	/// actually stored as string data.)
	/// <p/>
	/// A value may also consist of a set of other name-value pairs.  This
	/// makes it possible to nest collections of attributes, giving rise
	/// to a hierarchy of attributes.  OPeNDAP uses this structure to provide
	/// information about variables in a dataset.
	/// <p/>
	/// In the following example of a DAS, several of the attribute
	/// collections have names corresponding to the names of variables in
	/// a hypothetical dataset.  The attributes in that collection are said to
	/// belong to that variable.  For example, the <code>lat</code> variable has an
	/// attribute <code>units</code> of <code>degrees_north</code>.
	/// <p/>
	/// <blockquote><pre>
	/// Attributes {
	/// GLOBAL {
	/// String title "Reynolds Optimum Interpolation (OI) SST";
	/// }
	/// lat {
	/// String units "degrees_north";
	/// String long_name "Latitude";
	/// Float64 actual_range 89.5, -89.5;
	/// }
	/// lon {
	/// String units "degrees_east";
	/// String long_name "Longitude";
	/// Float64 actual_range 0.5, 359.5;
	/// }
	/// time {
	/// String units "days since 1-1-1 00:00:00";
	/// String long_name "Time";
	/// Float64 actual_range 726468., 729289.;
	/// String delta_t "0000-00-07 00:00:00";
	/// }
	/// sst {
	/// String long_name "Weekly Means of Sea Surface Temperature";
	/// Float64 actual_range -1.8, 35.09;
	/// String units "degC";
	/// Float64 add_offset 0.;
	/// Float64 scale_factor 0.0099999998;
	/// Int32 missing_value 32767;
	/// }
	/// }
	/// </pre></blockquote>
	/// <p/>
	/// Attributes may have arbitrary names, although in most datasets it
	/// is important to choose these names so a reader will know what they
	/// describe.  In the above example, the <code>GLOBAL</code> attribute provides
	/// information about the entire dataset.
	/// <p/>
	/// Data attribute information is an important part of the the data
	/// provided to a OPeNDAP client by a server, and the DAS is how this
	/// data is packaged for sending (and how it is received).
	/// 
	/// </summary>
	/// <author>  jehamby
	/// </author>
	/// <version>  $Revision: 15901 $
	/// </version>
	/// <seealso cref="DDS">
	/// </seealso>
	/// <seealso cref="AttributeTable">
	/// </seealso>
	/// <seealso cref="Attribute">
	/// </seealso>
	[Serializable]
	public class DAS:AttributeTable, System.ICloneable
	{
		
		
		// Used by resolveAliases() method
		private AttributeTable currentAT = null;
		private Alias currentAlias = null;
		
		/// <summary> Create a new empty <code>DAS</code>.</summary>
		public DAS():base("Attributes")
		{
			//attr = new SortedTable();
		}
		
		
		public override System.Object Clone()
		{
			DAS d = (DAS) base.Clone();
			return d;
		}
		
		
		/// <summary> Returns the <code>AttributeTable</code> with the given name.
		/// 
		/// </summary>
		/// <param name="name">the name of the <code>AttributeTable</code> to return.
		/// </param>
		/// <returns> the <code>AttributeTable</code> with the specified name, or null
		/// if there is no matching <code>AttributeTable</code>.
		/// </returns>
		/// <throws>  NoSuchAttributeException There is no AttributeTable with the passed name. </throws>
		/// <seealso cref="AttributeTable">
		/// </seealso>
		public AttributeTable getAttributeTable(System.String name)
		{
			AttributeTable at = null;
			Attribute a = getAttribute(name);
			if (a != null)
			{
				if (a.isContainer())
				{
					at = a.getContainer();
				}
			}
			return (at);
		}
		
		/// <summary> Returns the <code>AttributeTable</code> with the given name.
		/// 
		/// </summary>
		/// <param name="name">the name of the <code>AttributeTable</code> to return.
		/// </param>
		/// <returns> the <code>AttributeTable</code> with the specified name, or null
		/// if there is no matching <code>AttributeTable</code>.
		/// </returns>
		/// <seealso cref="AttributeTable">
		/// </seealso>
		public AttributeTable getAttributeTableN(System.String name)
		{
			AttributeTable at = null;
			Attribute a = getAttribute(name);
			if (a != null)
			{
				if (a.isContainer())
				{
					at = a.ContainerN;
				}
			}
			return (at);
		}
		
		/// <summary> Adds an <code>AttributeTable</code> to the DAS.
		/// 
		/// </summary>
		/// <param name="name">the name of the <code>AttributeTable</code> to add.
		/// </param>
		/// <param name="a">   the <code>AttributeTable</code> to add.
		/// </param>
		/// <seealso cref="AttributeTable">
		/// </seealso>
		public virtual void  addAttributeTable(System.String name, AttributeTable a)
		{
			addContainer(name, a);
		}
		
		/// <summary> Reads a <code>DAS</code> from the named <code>InputStream</code>.  This
		/// method calls a generated parser to interpret an ASCII representation of a
		/// <code>DAS</code>, and regenerate that <code>DAS</code> in memory.
		/// 
		/// </summary>
		/// <param name="is">the <code>InputStream</code> containing the <code>DAS</code> to
		/// parse.
		/// </param>
		/// <throws>  ParseException error in parser. </throws>
		/// <throws>  DASException   error in constructing <code>DAS</code>. </throws>
		/// <throws>  NDODS.DAP.Parser.TokenMgrError </throws>
		/// <summary>                        error in token manager
		/// (unterminated quote).
		/// </summary>
		/// <seealso cref="NDODS.DAP.Parser.DASParser">
		/// </seealso>
		public virtual void  parse(System.IO.Stream is_Renamed)
		{
			DASParser dp = new DASParser(is_Renamed);
			dp.Attributes(this);
			
			resolveAliases();
		}
		
		
		/// <summary> This method searchs through the <code>DAS</code>
		/// for Alias members. When an Alias is found the method attempts to
		/// resolve it to a specific Attribute.
		/// <p/>
		/// This method is invoked by <code>parse(InputStream is)</code>, and is
		/// used to Search for Aliases in AttributeTables found in the DAS.
		/// <p/>
		/// If you are building a DAS from it's API it is important to call
		/// this method prior to returning said DAS to an application. If
		/// this call is not made, Aliases will not work correctly.
		/// 
		/// </summary>
		/// <seealso cref="Alias">
		/// </seealso>
		/// <seealso cref="DDS.resolveAliases()">
		/// </seealso>
		public virtual void  resolveAliases()
		{
			
			resolveAliases(this);
			
			// Enforce the rule that Aliases at the highest level of the DAS
			// must point to a container (AttributeTable)
			System.Collections.IEnumerator e = Names.GetEnumerator();
			//UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
			while (e.MoveNext())
			{
				//UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
				System.String aName = (System.String) e.Current;
				
				if (Debug.isSet("DAS"))
					System.Console.Out.WriteLine("DAS.resolveAliases() - aName: " + aName);
				
				Attribute at = getAttribute(aName);
				if (!at.isContainer())
				{
					
					throw new MalformedAliasException("Aliases at the top-level of a DAS MUST reference a container (AttributeTable), not a simple Attribute");
				}
			}
		}
		
		/// <summary> This method recursively searchs through the passed <code>AttributeTable</code> parameter
		/// at for Alias members. When an Alias is found the method attempts to
		/// resolve it to a specific Attribute.
		/// <p/>
		/// This method gets called is invoked by <code>reolveAliases(BaseType bt)</code>, and is
		/// used to Search for Aliases in AttributeTables found in a BaseTypes Attributes.
		/// <p/>
		/// This method manipulates the global variable <code>currentBT</code>.
		/// 
		/// </summary>
		/// <param name="at">The <code>AttributeTable</code> in which to Search for and resolve Alias members
		/// </param>
		
		
		private void  resolveAliases(AttributeTable at)
		{
			
			// Cache the current (parent) Attribute table. This value is
			// null if this method is called from parse();
			AttributeTable cacheAT = currentAT;
			
			// Set the current AttributeTable to the one that we are searching.
			currentAT = at;
			
			if (Debug.isSet("DAS"))
			{
				//UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
				System.Console.Out.WriteLine("DAS.resolveAliases(at=" + at + ")");
			}
			
			//getall of the Attributes from the table.
			System.Collections.IEnumerator aNames = at.Names.GetEnumerator();
			//UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
			while (aNames.MoveNext())
			{
				
				//UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
				System.String aName = (System.String) aNames.Current;
				if (Debug.isSet("DAS"))
				{
					//UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
					System.Console.Out.WriteLine("DAS.resolveAliases(at=" + at + ") - aName: " + aName);
				}
				
				NDODS.DAP.Attribute thisA = currentAT.getAttribute(aName);
				
				if (Debug.isSet("DAS"))
					System.Console.Out.WriteLine("thisA.getClass().getName(): " + thisA.GetType().FullName);
				
				if (thisA.isAlias())
				{
					//Is Alias? Resolve it!
					resolveAlias((Alias) thisA);
					if (Debug.isSet("DAS"))
						System.Console.Out.WriteLine("Resolved Alias: '" + thisA.Name + "'\n");
				}
				else if (thisA.isContainer())
				{
					//Is AttributeTable (container)? Search it!
					
					resolveAliases(thisA.getContainer());
				}
			}
			
			// Restore the previous currentAT state.
			currentAT = cacheAT;
		}
		
		
		/// <summary> This method attempts to resolve the past Alias to a specific Attribute in the DDS.
		/// It does this by:
		/// <ul>
		/// <li>1) Tokenizing the Alias's variable and attribute fields (see <code>Alias</code>) </li>
		/// <li>2) Evaluating the tokenized fields to determine if the Alias is defined in terms
		/// of a relative or absolute path </li>
		/// <li>3) Searching the DAS or the currentAT (depending on results
		/// of 2) for the target Attribute </li>
		/// <li>4) Setting the Aliases intrnal reference to it's Attribute
		/// </ul>
		/// <p/>
		/// If an Attribute matching the definition of the Alias cannot be located,
		/// an Exception is thrown
		/// 
		/// </summary>
		/// <param name="alias">The <code>Alias</code> which needs to be resolved
		/// </param>
		
		private void  resolveAlias(Alias alias)
		{
			
			//Get the crucial stuff out of the Alias
			System.String name = alias.ClearName;
			System.String attribute = alias.AliasedToAttributeFieldAsClearString;
			
			// Get ready!
			System.Collections.IEnumerator e = null;
			currentAlias = alias;
			
			if (Debug.isSet("DAS"))
				System.Console.Out.WriteLine("\n\nFound: Alias " + name + "  " + attribute);
			
			// Let's go
			// see if we can find an Attribute within that DAS that matches the attribute field
			// in the Alias declartion.
			
			// The Attribute field MAY NOT be empty.
			if (attribute.Equals(""))
			{
				throw new MalformedAliasException("The attribute 'attribute' in the Alias " + "element must have a value other than an empty string.");
			}
			
			
			if (Debug.isSet("DAS"))
				System.Console.Out.WriteLine("Attribute: `" + attribute + "'");
			
			// Tokenize the attribute field.
			
			System.Collections.ArrayList aNames = NDODS.DAP.DDS.tokenizeAliasField(attribute);
			
			if (Debug.isSet("DAS"))
			{
				System.Console.Out.WriteLine("Attribute name tokenized to " + aNames.Count + " elements");
				e = aNames.GetEnumerator();
				//UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
				while (e.MoveNext())
				{
					//UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
					System.String aname = (System.String) e.Current;
					System.Console.Out.WriteLine("name: " + aname);
				}
			}
			
			
			NDODS.DAP.Attribute targetAT = null;
			
			// Absolute paths for attributes names must start with the dot character.
			bool isAbsolutePath = aNames[0].Equals(".");
			
			if (isAbsolutePath)
			{
				//Is it an absolute path?
				
				if (aNames.Count == 1)
				{
					throw new MalformedAliasException("Aliases must reference an Attribute. " + "An attribute field of dot (.) references the entire " + "DAS, which is not allowed.");
				}
				else
				{
					// Dump the dot from the vector of tokens and go try to find
					// the Attribute in the DAS.
					aNames.RemoveAt(0);
					
					targetAT = getAliasAttribute(this, aNames);
				}
			}
			else
			{
				throw new MalformedAliasException("In the Alias '" + name + "'" + " the attribute 'attribute' does not begin with the character dot (.). " + "The 'attribute' field must always be an absoulute path name from the " + "top level of the dataset, and thus must always begin with the dot (.) character.");
			}
			
			alias.setMyAttribute(targetAT);
		}
		
		
		/// <summary> This method executes a (recursive) Search of the <code>AttributeTable</code>
		/// parameter <b>at</b> for an <code>Attribute</code> whose name resolves to
		/// the vector of names contained in the <code>Vector</code> parameter
		/// <b>aNames</b>. An Attribute is considered a match if each of it's node
		/// names in the hierarchy of AttributeTables contained in the
		/// one passed as parameter <b>at</b> matches (equals) the corresponding name
		/// in the Vector <b>aNames</b>.
		/// 
		/// </summary>
		/// <param name="att">   The <code>AttributeTable</code> to Search
		/// </param>
		/// <param name="aNames">The <code>Vector</code> of names to match to the nodes of <b>at</b>
		/// </param>
		
		private NDODS.DAP.Attribute getAliasAttribute(AttributeTable att, System.Collections.ArrayList aNames)
		{
			
			// Get the first node name form the vector.
			System.String aName = (System.String) aNames[0];
			
			// Get the list of child nodes from the AttributeTable
			System.Collections.IEnumerator e = att.Names.GetEnumerator();
			//UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
			while (e.MoveNext())
			{
				
				// Get an Attribute
				//UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
				System.String atName = (System.String) e.Current;
				NDODS.DAP.Attribute a = att.getAttribute(atName);
				
				// Get the Attributes name and Normalize it.
				System.String normName = NDODS.DAP.DDS.normalize(a.Name);
				
				// Are they the same?
				if (normName.Equals(aName))
				{
					
					// Make sure this reference doesn't pass through an Alias.
					if (a.isAlias())
					{
						throw new MalformedAliasException("Aliases may NOT point to other aliases");
					}
					
					//dump the name from the list of names.
					aNames.RemoveAt(0);
					
					// Are there more?
					if (aNames.Count == 0)
					{
						//No! We found it!
						return (a);
					}
					else if (a.isContainer())
					{
						// Is this Attribute a container (it better be)
						
						try
						{
							// Recursively Search for the rest of the name vector in the container.
							return (getAliasAttribute(a.getContainer(), aNames));
						}
						catch (NoSuchAttributeException nsae)
						{
							throw new MalformedAliasException("Attribute " + a.Name + " is not an attribute container. (AttributeTable) " + " It may not contain the attribute: " + aName);
						}
					}
					else
					{
						// Dead-end, through an exception!
						
						throw new MalformedAliasException("Attribute " + a.Name + " is not an attribute container. (AttributeTable) " + " It may not contain the attribute: " + aName);
					}
				}
			}
			// Nothing Matched, so this Search failed.
			throw new UnresolvedAliasException("The alias `" + currentAlias.Name + "` references the attribute: `" + aName + "` which cannot be found.");
		}
	}
}